/*
  The MIT License
  
  Copyright (c) 2009 smath project
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, andor sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*/
package pl.smath.expression.parser.text;

import java.util.Vector;

import pl.smath.expression.model.AbsTerm;
import pl.smath.expression.model.BooleanTerm;
import pl.smath.expression.model.ConjunctionTerm;
import pl.smath.expression.model.DisjunctionTerm;
import pl.smath.expression.model.ExponentiationTerm;
import pl.smath.expression.model.FractionTerm;
import pl.smath.expression.model.GapTerm;
import pl.smath.expression.model.InTerm;
import pl.smath.expression.model.NumberTerm;
import pl.smath.expression.model.ProductTerm;
import pl.smath.expression.model.RelationTerm;
import pl.smath.expression.model.RootTerm;
import pl.smath.expression.model.SetTerm;
import pl.smath.expression.model.SubscriptTerm;
import pl.smath.expression.model.SumTerm;
import pl.smath.expression.model.Term;
import pl.smath.expression.model.VariableTerm;
import pl.smath.expression.parser.ExpressionParserException;


/**
 * Parsuje wyrazenie dostarczone jako tekst
 * @author klangner
 *
 */
public class TextParser {
	
	/**
	 * tokenizer do pobierania token z wejscia
	 */
	private Tokenizer	tokenizer;

	
	/**
	 * Parse given expression
	 * @return Root node
	 * @throws ExpressionParserException 
	 */
	public Term parse(String exp) throws ExpressionParserException{

		tokenizer = new Tokenizer(exp);
		return disjunction();
	}

	/**
	 * Pobranie iloczynu logicznego (and)
	 * @throws ExpressionParserException 
	 */
	private Term disjunction() throws ExpressionParserException
	{
	    Vector<Term> terms = new Vector<Term>();
	    Token token;

	    if(!tokenizer.hasMoreTokens()){
	    	throw new ExpressionParserException("Any token", null);
	    }
	    
	    terms.add(conjunction());

	    while(tokenizer.hasMoreTokens()){
	    	
	    	token = tokenizer.nextToken();
	    	if(token.getText().compareTo("\\vee") == 0){
	    		terms.add(conjunction());
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }

	    if(terms.size() > 1){
	    	DisjunctionTerm term = new DisjunctionTerm();
	    	term.addTerms(terms);
	    	return term;
	    }
	    else if(terms.size() == 1){
	    	return terms.get(0);
	    }
	    else{
	    	throw new ExpressionParserException("Any token", null);
	    }
	    
	}
	
	
	/**
	 * Pobranie iloczynu logicznego (and)
	 * @throws ExpressionParserException 
	 */
	private Term conjunction() throws ExpressionParserException
	{
	    Vector<Term> terms = new Vector<Term>();
	    Token token;

	    if(!tokenizer.hasMoreTokens()){
	    	throw new ExpressionParserException("Any token", null);
	    }
	    
	    terms.add(relation());

	    while(tokenizer.hasMoreTokens()){
	    	
	    	token = tokenizer.nextToken();
	    	if(token.getText().compareTo("\\wedge") == 0){
	    		terms.add(relation());
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }

	    if(terms.size() > 1){
	    	ConjunctionTerm term = new ConjunctionTerm();
	    	term.addTerms(terms);
	    	return term;
	    }
	    else if(terms.size() == 1){
	    	return terms.get(0);
	    }
	    else{
	    	throw new ExpressionParserException("Any token", null);
	    }
	    
	}
	
	
	/**
	 * Pobranie relacji
	 * @return
	 * @throws ExpressionParserException 
	 */
	private Term relation() throws ExpressionParserException{
		Term output;
		Term left = sum();
		
		output = left;
		if(tokenizer.hasMoreTokens()){
			Token token = tokenizer.nextToken();
			if(token instanceof RelationToken){
				Term right = sum();
				output = new RelationTerm(token.getText(), left, right);
			}
			else if(token.getText().compareTo("\\in") == 0){
				Term right = sum();
				output = new InTerm(left, right);
			}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    	}
		}
		
		return output;
	}	
	
	/**
	 * Pobranie sumy wyrazen z wejscia
	 * @return
	 * @throws ExpressionParserException 
	 */
	private Term sum() throws ExpressionParserException{
		
		Vector<Term> terms = new Vector<Term>();
		Token token;
		Term term;

		term = multi();
		terms.add(term);
	    
	    while(tokenizer.hasMoreTokens()){
	    	
	    	token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '+'){
	    		terms.add(multi());
	    	}
	    	else if(token.getText().charAt(0) == '-'){
	    		term = multi();
	    		term.setNegative(!term.isNegative());
	    		terms.add(term);
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
	    
	    if(terms.size() > 1){
	    	SumTerm sumTerm = new SumTerm();
	    	sumTerm.addTerms(terms);
	    	return sumTerm;
	    }
	    else{
	    	return terms.get(0);
	    }
	}

	
	/**
	 * Pobranie iloczynu wyrazen z wejscia
	 * @return
	 * @throws ExpressionParserException 
	 */
	private Term multi() throws ExpressionParserException
	{
	    Vector<Term> terms = new Vector<Term>();
	    Token token;
	    boolean isNegative = false;

	    if(!tokenizer.hasMoreTokens()){
	    	throw new ExpressionParserException("Any token", null);
	    }
	    
		token = tokenizer.nextToken();

	    if(token == null){
	    	throw new ExpressionParserException("Any token", null);
	    }
	    
		while(token.getText().charAt(0) == '-'){
			isNegative = !isNegative;
			if(!tokenizer.hasMoreTokens()){
		    	throw new ExpressionParserException("Any token", null);
		    } else {
		    	token = tokenizer.nextToken();
		    }
		}
			
		tokenizer.pushback(token);
	    
	    terms.add(fraction());

	    while(tokenizer.hasMoreTokens()){
	    	
	    	token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '*'){
	    		terms.add(fraction());
	    	}
	    	else if( token instanceof SymbolToken || 
	    			(token instanceof BracketToken && token.getText().charAt(0) == '(') )
	    	{
	    		tokenizer.pushback(token);
	    		terms.add(fraction());
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
	    
	    if(terms.size() > 1){
	    	ProductTerm term = new ProductTerm();
	    	term.addTerms(terms);
	    	term.setNegative(isNegative != term.isNegative());
	    	return term;
	    }
	    else{
	    	terms.get(0).setNegative(isNegative != terms.get(0).isNegative());
	    	return terms.get(0);
	    }
	    
	}
	
	
	/**
	 * Pobranie iloczynu wyrazen z wejscia
	 * @return
	 * @throws ExpressionParserException 
	 */
	private Term fraction() throws ExpressionParserException
	{
	    Term denominator;
	    Term term;

	    term = pow();

	    while(tokenizer.hasMoreTokens()){
	    	
	    	Token token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '/' || token.getText().charAt(0) == ':'){
	    		denominator = pow();
	    		term = new FractionTerm(token.getText(), term, denominator);
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
    	
    	return term;
	}
	
	
	/**
	 * Pobranie elementu a^b
	 * @return
	 * @throws ExpressionParserException 
	 */
	private Term pow() throws ExpressionParserException
	{
	    Term exponent;
	    Term term;
	    Token 	token;

		term = unary();

	    while(tokenizer.hasMoreTokens()){
	    	
	    	token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '#'){
	    		exponent = unary();
	    		term = new SubscriptTerm(term, exponent);
	    	}
	    	else if(token.getText().charAt(0) == '^'){
	    		exponent = unary();
	    		term = new ExponentiationTerm(term, exponent);
	    	}
	    	else if(token.getText().charAt(0) == '$'){
	    		exponent = unary();
	    		term = new RootTerm(term, exponent);
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
    	
    	return term;
	}
	
	/**
	 * Pobranie elementu poprzedzonego znakiem -
	 * @return
	 * @throws ExpressionParserException 
	 */
	private Term unary() throws ExpressionParserException{
	    
		Token 	token = tokenizer.nextToken();
		
		if(token != null){
			if(token.getText().charAt(0) == '-'){
				Term 	term = element();
				term.setNegative(true);
				return term;
			}
			else{
				tokenizer.pushback(token);
				return element();
			}
		}
		
		throw new ExpressionParserException("Any element", null);
	}

	
	/**
	 * Pobranie elementu z wejscia
	 * Elementem jest:
	 * - liczba
	 * - zmienna
	 * - wyrazenie w nawiasach
	 * - zbiór
	 * @throws ExpressionParserException 
	 */
	private Term element() throws ExpressionParserException
	{
		Token token = tokenizer.nextToken();
		
		if(token instanceof NumberToken){
			return new NumberTerm( token.getText() );
		}
		else if(token instanceof SymbolToken){
			return new VariableTerm( token.getText() );
		}
		else if(token instanceof BooleanToken){
			return new BooleanTerm( token.getText() );
		}
		else if(token instanceof KeywordToken){
			return keyword( token );
		}
		else if(token.getText().charAt(0) == '('){

			Term term = disjunction();
			skip(")");
			return term;
		}
		else if(token.getText().charAt(0) == '|'){

			Term term = sum();
			skip("|");
			return new AbsTerm(term);
		}
		else if(token.getText().charAt(0) == '{'){

			SetTerm setTerm = new SetTerm();
			do{
				setTerm.addTerm( sum() );
				token = tokenizer.nextToken();
			}
			while(token.getText().charAt(0) == ',');
			
			if(token.getText().charAt(0) != '}'){
				throw new ExpressionParserException("}", token.getText());
			}
				
			return setTerm;
		}
		
		throw new ExpressionParserException("Any element", token.getText());
		
	}

	/**
	 * Pobiera słowo kluczowe
	 * @param keywordToken
	 * @return
	 * @throws ExpressionParserException 
	 */
	private Term keyword(Token keywordToken) throws ExpressionParserException {
		
		if(keywordToken.getText().compareToIgnoreCase("\\sqrt") == 0){
			
			Term pow = new NumberTerm(2);
			Token token = tokenizer.nextToken();
			if(token.getText().charAt(0) == '['){
				pow = sum();
				skip("]");
				skip("{");
			}
			else if(token.getText().charAt(0) != '{'){
				throw new ExpressionParserException("{ or [", token.getText());
			}

			Term term = sum();
			skip("}");
			return new RootTerm(term, pow);
		}
		else if(keywordToken.getText().compareToIgnoreCase("\\frac") == 0){
			skip("{");
			Term nominator = sum();
			skip("}");
			skip("{");
			Term denominator = sum();
			skip("}");
			return new FractionTerm(nominator, denominator);
		} else if (keywordToken.getText().compareToIgnoreCase("\\box") == 0){
			return new GapTerm(); 
		}

		throw new ExpressionParserException("Keyword", keywordToken.getText());
	}

	/**
	 * Przeskakuje token.
	 * Jeżeli token jest rożny od spodziewanego to wysylane jest exception
	 * @throws ExpressionParserException 
	 */
	private void skip(String expected) throws ExpressionParserException {
		
		Token token = tokenizer.nextToken();
		if (token == null )
			throw new ExpressionParserException(expected, "null");
		else if(token.getText().compareTo(expected) != 0){
			throw new ExpressionParserException(expected, token.getText());
		}
	}


}
